#include "Avalon/UidService/UidServer.hpp"
#include <boost/functional/factory.hpp>
#include "Avalon/Base/Algorithm.hpp"
#include "Avalon/IO/Channel.hpp"
#include "Avalon/IO/ChannelAdapterServerConnection.hpp"
#include "Avalon/IO/ConnectException.hpp"
#include "Avalon/IO/ServerConnection.hpp"
#include "Avalon/ServiceLocator/ServiceLocatorClient.hpp"
#include "Avalon/Threading/LockRelease.hpp"
#include "Avalon/UidService/UidClientChannel.hpp"
#include "Avalon/UidService/UidDataStore.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Serialization;
using namespace Avalon::ServiceLocator;
using namespace Avalon::Services;
using namespace Avalon::Threading;
using namespace Avalon::UidService;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::signals2;
using namespace std;

UidServer::UidServer(ServiceLocatorClient* serviceClient,
    UidDataStore* dataStore, ServerConnection* connection,
    DataShuttleFactory* dataShuttleFactory, Timer* heartbeatTimer,
    time_duration timeout, ThreadPool& threadPool)
    : ServerMixin(serviceClient, new ChannelAdapterServerConnection(connection,
        new WrapperChannelFactory(WrapperChannelFactory::ChannelCreator(
        bind(factory<UidClientChannel*>(), _1, ref(threadPool))))),
        dataShuttleFactory, heartbeatTimer, timeout, threadPool),
      m_dataStore(dataStore),
      m_signalHandler(threadPool) {
  SetHandler<ReserveUidsService>(m_signalHandler, m_mutex,
    bind(&UidServer::OnReserveUidsRequest, this, _1, _2));
}

UidServer::~UidServer() {
  Close();
}

bool UidServer::IsConnected() const {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  return ServerMixin::IsConnected();
}

void UidServer::Start() {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(!ServerMixin::Start(m_signalHandler, lock)) {
    return;
  }
  connection closedConnection = m_dataStore->ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(
    bind(&UidServer::OnDataStoreClosed, this)));
  try {
    m_dataStore->Connect();
  } catch(std::exception&) {
    CloseResources();
    WaitForClose(lock);
    BOOST_RETHROW;
  }
  m_dataStoreState.Initialize(true, closedConnection);
}

void UidServer::Close() {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  ServerMixin::Close(lock);
}

void UidServer::HandleCloseResources() {
  if(m_dataStoreState.IsOpen()) {
    m_dataStore->Close();
    return;
  }
}

void UidServer::OnDataStoreClosed() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_dataStoreState.SetClosed();
  CloseResources();
}

void UidServer::OnReserveUidsRequest(
    const RequestToken<ReserveUidsService>& request, long long blockSize) {
  UidClientChannel* channel = static_cast<UidClientChannel*>(
    request.GetChannel());
  if(!channel->IsLoggedIn()) {
    request.SendResponse(make_tuple(false, "Not logged in.", 0));
    return;
  }
  try {
    long long reserveResult = m_dataStore->Reserve(blockSize);
    request.SendResponse(make_tuple(true, "", reserveResult));
  } catch(std::exception& e) {
    request.SendResponse(make_tuple(false, e.what(), 0));
  }
}
